#include "CairoParser.h"
#include "ByteArray.h"
#include "Color.h"
#include <cairo/cairo.h>
#include <math.h>
#include <iostream>
using namespace std;

unsigned int TYPE_GraphicsSolidFill 	= 1;
unsigned int TYPE_GraphicsGradientFill	= 2;
unsigned int TYPE_GraphicsStroke		= 3;
unsigned int TYPE_GraphicsPath 			= 4;

CairoParser::CairoParser(const char *fileName, float resolution)
{
    _resolution = 1;
    if( ba.readBytesFromFile(fileName) ){
        _resolution = resolution;
        _offsetX = ba.readFloat();
        _offsetY = ba.readFloat();

        unsigned int width = ba.readFloat() * _resolution;
        unsigned int height = ba.readFloat() *_resolution;

        cairo_surface_t *surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height );
        cout<<"cairo_surface_t *surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, "<<width<<","<< height<<" );"<<endl;
        cr = cairo_create(surface);

        cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
        cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);

        while(!ba.isFinished()){
            unsigned int graphicType = ba.readUnsignedByte();
            //cout<<endl<<"graphicType "<<graphicType<<endl;
            if (graphicType == TYPE_GraphicsSolidFill){
                _fillType = TYPE_GraphicsSolidFill;
                readGraphicsSolidFill();
            }
            else if (graphicType == TYPE_GraphicsGradientFill){
               _fillType = TYPE_GraphicsGradientFill;
                readGraphicsGradientFill();
            }
            else if (graphicType == TYPE_GraphicsStroke){
                _fillType = TYPE_GraphicsStroke;
                readGraphicsStroke();
            }
            else if (graphicType == TYPE_GraphicsPath)	{
                readGraphicsPath();
            }
            else{
                cout<<endl<<"ERROR"<<endl;
            }

        }

        cairo_destroy(cr);
        cairo_surface_write_to_png(surface, "hello.png");
        cairo_surface_destroy(surface);
    }
}

void CairoParser::readGraphicsSolidFill()
{
    unsigned int color = ba.readUnsignedInt();
    cairo_set_source_rgba( cr, colorManager.getRedPercent(color), colorManager.getGreenPercent(color), colorManager.getBluePercent(color), colorManager.getAlphaPercent(color) );
    cout<<"cairo_set_source_rgba( cr,"<< colorManager.getRedPercent(color)<<","<< colorManager.getGreenPercent(color)<<","<< colorManager.getBluePercent(color)<<","<< colorManager.getAlphaPercent(color)<<" );"<<endl;
}

void CairoParser::readGraphicsGradientFill()
{
    unsigned int spread = ba.readUnsignedByte();
    bool interpolation = ba.readBoolean();
    float focalPointRation = ba.readFloat();

    int gradientType = ba.readUnsignedByte();
    float matrixA = ba.readFloat()*_resolution;
    float matrixB = ba.readFloat()*_resolution;
    float matrixC = ba.readFloat()*_resolution;
    float matrixD = ba.readFloat()*_resolution;
    float matrixTx = (ba.readFloat() - _offsetX)*_resolution;
    float matrixTy = (ba.readFloat() - _offsetY)*_resolution;

    double rotation = atan(matrixB/matrixA);
    if(matrixA<0){
        rotation += PI;
    }
    double cosRot = cos(rotation);
    double sinRot = sin(rotation);

    float w = (matrixA / cosRot ) * GRADIENT_CONST;
    float h = (-matrixC / sinRot ) * GRADIENT_CONST;

    if(gradientType == 1){
        float halfWCos = (w / 2) * cosRot;
        float halfHSin = (h / 2) * sinRot;

        double leftPointX = matrixTx - halfWCos;
        double leftPointY = matrixTy - halfHSin;
        double rightPointX = matrixTx + halfWCos;
        double rightPointY = matrixTy + halfHSin;

        pat = cairo_pattern_create_linear(leftPointX, leftPointY, rightPointX, rightPointY);
    }
    else{
        pat = cairo_pattern_create_radial(matrixTx, matrixTy, 0 ,matrixTx, matrixTy, w/2);
    }

    unsigned int colorsAmount = ba.readUnsignedByte();
    for (unsigned int i = 0; i < colorsAmount; i++) {
        unsigned int colorGradient = ba.readUnsignedInt();
        unsigned int gradientRatio = ba.readUnsignedByte();
        cairo_pattern_add_color_stop_rgba(pat, gradientRatio/255, colorManager.getRedPercent(colorGradient), colorManager.getGreenPercent(colorGradient), colorManager.getBluePercent(colorGradient), colorManager.getAlphaPercent(colorGradient));
    }
    cairo_set_source (cr, pat);
}

void CairoParser::readGraphicsStroke()
{
    double tickness = ba.readFloat()*_resolution;
    cairo_set_line_width( cr, tickness );
    cout<<"cairo_set_line_width( cr,"<< tickness <<" );"<<endl;
    readGraphicsSolidFill();
}

void CairoParser::readGraphicsPath()
{
    unsigned int i;
    unsigned int dataI = 0;

    short dataSize = ba.readShort();
    vector<double> dataNumbers(dataSize);
    for (i = 0; i < dataSize; i++) {
        if((i&1) != 1){
            dataNumbers[i] = (double(ba.readShort()) * 0.1 - _offsetX) * _resolution;
        }
        else{
            dataNumbers[i] = (double(ba.readShort()) * 0.1 - _offsetY) * _resolution;
        }
    }

    unsigned int c = 1;
    unsigned int o = 0;
    short commandsSize = ba.readShort();
    vector<unsigned int> commands(commandsSize);
    for (i = 0; i < commandsSize; i++) {
        if (--c == 0) {
            o = ba.readUnsignedByte();
            c = 4;
        }
        commands[i] = (o & 3);
        o = o >> 2;

        if (commands[i] == 1) {
            double moveToX = dataNumbers[dataI++];
            double moveToY = dataNumbers[dataI++];
            cairo_move_to(cr, moveToX, moveToY);
            cout<<"cairo_move_to(cr,"<< moveToX<<","<< moveToY<<");"<<endl;
        }
        else if (commands[i] == 2) {
            double lineToX = dataNumbers[dataI++];
            double lineToY = dataNumbers[dataI++];
            cairo_line_to(cr, lineToX, lineToY );
            cout<<"cairo_line_to(cr,"<< lineToX<<","<< lineToY<<");"<<endl;
        }
        else if (commands[i] == 3) {
            //Converting Quadratic curve to Cubic curve
            double QP0X = dataNumbers[dataI-2];
            double QP0Y = dataNumbers[dataI-1];

            double QP1X = dataNumbers[dataI];
            double QP1Y = dataNumbers[dataI+1];

            double QP2X = dataNumbers[dataI+2];
            double QP2Y = dataNumbers[dataI+3];

            dataI+=4;

            double CP1X = QP0X + ( TWO_THIRD * (QP1X-QP0X) );
            double CP1Y = QP0Y + ( TWO_THIRD * (QP1Y-QP0Y) );

            double CP2X = QP2X + ( TWO_THIRD * (QP1X-QP2X) );
            double CP2Y = QP2Y + ( TWO_THIRD * (QP1Y-QP2Y) );

            cairo_curve_to(cr, CP1X, CP1Y, CP2X, CP2Y, QP2X, QP2Y);
            cout<<"cairo_curve_to(cr,"<< CP1X<<","<< CP1Y<<","<< CP2X<<","<< CP2Y<<","<< QP2X<<","<< QP2Y<<");"<<endl;
        }
    }

    unsigned int winding = ba.readUnsignedByte(); //2 == EvenOdd

    if(winding == 2){
        cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
        cout<<"cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);"<<endl;
    }
    else {
        cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
        cout<<"cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);"<<endl;
    }

    if(_fillType == TYPE_GraphicsSolidFill){
        cairo_fill(cr);
        cout<<"cairo_fill(cr);"<<endl;
    }
    else if (_fillType == TYPE_GraphicsGradientFill) {
        cairo_fill(cr);
        cairo_pattern_destroy (pat);
        cout<<"cairo_fill(cr);"<<endl;
        cout<<"cairo_pattern_destroy (pat);"<<endl;
    }
    else {
        cairo_stroke(cr);
        cout<<"cairo_stroke(cr);"<<endl;
    }
}
